package com.sheng.project.study.spring.demo02;

import com.sheng.project.study.spring.demo02.module1.Module1Config;
import com.sheng.project.study.spring.demo02.module2.Module2Config;
import com.sheng.project.study.spring.demo02.module2.Service3;
import org.junit.Test;
import org.springframework.beans.factory.BeanFactoryUtils;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.Arrays;
import java.util.Map;

/**
 * @author liusheng
 * @date 2023/07/10
 */
public class ParentFactoryTest {

    @Test
    public void test1() {
        // 定义容器
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
        // 注册 bean
        context.register(Module1Config.class, Module2Config.class);
        // 启动容器
        context.refresh();
    }

    /**
     * 父容器和子容器是相互隔离的，他们内部可以存在名称相同的 bean
     * 子容器可以访问父容器中的 bean，而父容器不能访问子容器中的 bean
     * 调用子容器的 getBean 方法获取 bean 的时候，会沿着当前容器开始向上面的容器进行查找，直到找到对应的 bean 为止
     * 子容器中可以通过任何注入方式注入父容器中的 bean，而父容器中是无法注入子容器中的 bean，原因是第 2 点
     */
    @Test
    public void test2() {
        // 创建父容器
        AnnotationConfigApplicationContext parentContext = new AnnotationConfigApplicationContext();
        // 向父容器中注册 Module1Config 配置类
        parentContext.register(Module1Config.class);
        // 启动父容器
        parentContext.refresh();
        // 创建子容器
        AnnotationConfigApplicationContext childContext = new AnnotationConfigApplicationContext();
        // 向子容器中注册 Module2Config 配置类
        childContext.register(Module2Config.class);
        // 给子容器设置父容器
        childContext.setParent(parentContext);
        // 启动子容器
        childContext.refresh();
        // 从子容器中获取 Service3
        Service3 service3 = childContext.getBean(Service3.class);
        System.out.println(service3.m1());
        System.out.println(service3.m2());
    }

    @Test
    public void test3() {
        // 创建父容器 parentFactory
        DefaultListableBeanFactory parentFactory = new DefaultListableBeanFactory();
        // 向父容器 parentFactory 注册一个 bean[userName->"黄金鹏"]
        parentFactory.registerBeanDefinition("userName",
                BeanDefinitionBuilder.
                        genericBeanDefinition(String.class).
                        addConstructorArgValue("黄金鹏").
                        getBeanDefinition());
        // 创建一个子容器 childFactory
        DefaultListableBeanFactory childFactory = new DefaultListableBeanFactory();
        // 调用 setParentBeanFactory 指定父容器
        childFactory.setParentBeanFactory(parentFactory);
        // 向子容器 parentFactory 注册一个 bean[address->"深圳"]
        childFactory.registerBeanDefinition("address",
                BeanDefinitionBuilder.
                        genericBeanDefinition(String.class).
                        addConstructorArgValue("深圳").
                        getBeanDefinition());
        System.out.println("获取bean【userName】：" + childFactory.getBean("userName"));
        // 有没有方式解决 ListableBeanFactory 接口不支持层次查找的问题
        System.out.println(Arrays.asList(childFactory.getBeanNamesForType(String.class)));

        // 层次查找所有符合类型的 bean 名称
        String[] beanNamesForTypeIncludingAncestors = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(childFactory, String.class);
        System.out.println(Arrays.asList(beanNamesForTypeIncludingAncestors));
        Map<String, String> beansOfTypeIncludingAncestors = BeanFactoryUtils.beansOfTypeIncludingAncestors(childFactory, String.class);
        System.out.println(Arrays.asList(beansOfTypeIncludingAncestors));

        // 问题 1：springmvc 中只使用一个容器是否可以？
        // 只使用一个容器是可以正常运行的。
        //
        // 问题 2：那么 springmvc 中为什么需要用到父子容器？
        // 通常我们使用 springmvc 的时候，采用 3 层结构，controller 层，service 层，dao 层；
        // 父容器中会包含 dao 层和 service 层，而子容器中包含的只有 controller 层；
        // 这 2 个容器组成了父子容器的关系，controller 层通常会注入 service 层的 bean。
        // 采用父子容器可以避免有些人在 service 层去注入 controller 层的 bean，导致整个依赖层次是比较混乱的。
        // 父容器和子容器的需求也是不一样的，比如父容器中需要有事务的支持，会注入一些支持事务的扩展组件，
        // 而子容器中 controller 完全用不到这些，对这些并不关心，子容器中需要注入一下 springmvc 相关的 bean，
        // 而这些 bean 父容器中同样是不会用到的，也是不关心一些东西，
        // 将这些相互不关心的东西隔开，可以有效的避免一些不必要的错误，而父子容器加载的速度也会快一些。
    }
}
